﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing.Imaging;

namespace Server
{
    class CaptureClass
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            int left, top, right, bottom;

            public Rectangle ToRectangle()
            {
                return new Rectangle(left, top, right - left, bottom - top);
            }
        }


        /// <summary>
        /// user32.dll wrapper class
        /// </summary>
        private class User32
        {
            [DllImport("user32.dll")]
            public static extern IntPtr WindowFromPoint(Point pos);

            [DllImport("user32.dll")]
            public static extern IntPtr GetDC(IntPtr hWnd);

            [DllImport("user32.dll")]
            public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDc);

            [DllImport("user32.dll")]
            public static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);

            [DllImport("user32.dll")]
            public static extern bool ScreenToClient(IntPtr hWnd, out Point point);

            [DllImport("user32.dll")]
            public static extern bool IsIconic(IntPtr hWnd);

            [DllImport("user32.dll")]
            public static extern bool IsWindow(IntPtr hWnd);
        }

        /// <summary>
        /// gdi32.dll wrapper class
        /// </summary>
        private class Gdi32
        {
            public const int SRCCOPY = 13369376;

            [DllImport("gdi32.dll")]
            public static extern IntPtr CreateCompatibleDC(IntPtr hDC);

            [DllImport("gdi32.dll")]
            public static extern bool DeleteDC(IntPtr hDC);

            [DllImport("gdi32.dll")]
            public static extern IntPtr DeleteObject(IntPtr hObject);

            [DllImport("gdi32.dll")]
            public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int width, int height);

            [DllImport("gdi32.dll")]
            public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hObject);

            [DllImport("gdi32.dll")]
            public static extern bool BitBlt(IntPtr hdcDest, int xDest,
                       int yDest, int wDest, int hDest, IntPtr hdcSource,
                       int xSrc, int ySrc, int RasterOp);
        }

        public const int MaxWidth = 800;
        public const int MaxHeight = 600;


        /// <summary>
        /// Cattura il contenuto di una finestra
        /// </summary>
        /// <param name="hWnd">handle della finestra da catturare</param>
        /// <returns></returns>
        public static Bitmap CaptureWindow(IntPtr hWnd, out bool minimized)
        {
            minimized = false;
            if(User32.IsIconic(hWnd))
            {
                minimized = true;
                return null;
            }

            IntPtr hDc = User32.GetDC(hWnd);
            if(hDc == IntPtr.Zero)
                return null;

            RECT clientRect = new RECT();

            User32.GetClientRect(hWnd, out clientRect);

            Rectangle clientRectangle = clientRect.ToRectangle();

            IntPtr compDc = Gdi32.CreateCompatibleDC(hDc);
            IntPtr compBitmap = Gdi32.CreateCompatibleBitmap(hDc, clientRectangle.Width, clientRectangle.Height);
            if (compBitmap == IntPtr.Zero)
            {
                User32.ReleaseDC(hWnd, hDc);
                Gdi32.DeleteDC(compDc);
                return null;
            }

            IntPtr prevBmp = Gdi32.SelectObject(compDc, compBitmap);
            Gdi32.BitBlt(compDc, 0, 0, clientRectangle.Width, clientRectangle.Height, hDc, 0, 0, Gdi32.SRCCOPY);
            Gdi32.SelectObject(compDc, prevBmp);

            Bitmap bitmap = Bitmap.FromHbitmap(compBitmap);
            
            User32.ReleaseDC(hWnd, hDc);
            Gdi32.DeleteDC(compDc);
            Gdi32.DeleteObject(compBitmap);
            
            Point mouseClientPos = Cursor.Position;
            if (User32.ScreenToClient(hWnd, out mouseClientPos) != false)
            {
                try
                {
                    using (Graphics gfx = Graphics.FromImage(bitmap))
                    {
                        //gfx.FillRectangle(Brushes.Red, new Rectangle(mouseClientPos, new Size(10, 10)));
                        gfx.FillEllipse(Brushes.Red, mouseClientPos.X - 5, mouseClientPos.Y - 5, 10, 10);
                    }
                }
                catch
                {
                    //L'immagine ha pixelformat indicizzato, significa che è appena stata rimassimizzata, ignora
                }
                
            }
            
            Size bitmapSize = bitmap.Size;

            if (bitmapSize.Width > MaxWidth || bitmapSize.Height > MaxHeight)
            {
                float wMultiplier = (float)MaxWidth / bitmapSize.Width;
                float hMultiplier = (float)MaxHeight / bitmapSize.Height;
                float multiplier = wMultiplier < hMultiplier ? wMultiplier : hMultiplier;
                Size scaledSize = new Size((int)(bitmapSize.Width * multiplier), (int)(bitmapSize.Height * multiplier));
                Bitmap scaledBitmap = new Bitmap(bitmap, scaledSize);
                bitmap.Dispose();
                return scaledBitmap;
            }
            else
                return bitmap;
        }

        /// <summary>
        /// Cattura una porzione dello schermo
        /// </summary>
        /// <param name="captureRectangle">Rettangolo che identifica la porzione di schermo da catturare</param>
        /// <returns></returns>
        public static Bitmap CaptureScreen(Rectangle captureRectangle)
        {
            Size bitmapSize = new Size(captureRectangle.Width, captureRectangle.Height);
            Bitmap bitmap = new Bitmap(bitmapSize.Width, bitmapSize.Height);
            using (Graphics gfx = Graphics.FromImage(bitmap))
            {
                gfx.CopyFromScreen(captureRectangle.X, captureRectangle.Y, 0, 0, captureRectangle.Size);
                if(Cursor.Position.X >= captureRectangle.Left && Cursor.Position.X <= captureRectangle.Right
                            && Cursor.Position.Y >= captureRectangle.Top && Cursor.Position.Y <= captureRectangle.Bottom)
                        //gfx.FillRectangle(Brushes.Red, new Rectangle(Cursor.Position, new Size(10, 10)));
                    gfx.FillEllipse(Brushes.Red, Cursor.Position.X - captureRectangle.Left - 5, Cursor.Position.Y - captureRectangle.Top - 5, 10, 10);
            }
            if (bitmapSize.Width > MaxWidth || bitmapSize.Height > MaxHeight)
            {
                float wMultiplier = (float)MaxWidth / bitmapSize.Width;
                float hMultiplier = (float)MaxHeight / bitmapSize.Height;
                float multiplier = wMultiplier < hMultiplier ? wMultiplier : hMultiplier;
                Size scaledSize = new Size((int)(bitmapSize.Width * multiplier), (int)(bitmapSize.Height * multiplier));
                Bitmap scaledBitmap = new Bitmap(bitmap, scaledSize);
                bitmap.Dispose();
                return scaledBitmap;
            }
            else
                return bitmap;
        }

        /// <summary>
        /// Ottiene l'handle della finestra corrispondente al punto dello schermo specificato
        /// </summary>
        /// <param name="pos">Il punto</param>
        /// <returns></returns>
        public static IntPtr WindowFromPoint(Point pos)
        {
            return User32.WindowFromPoint(pos);
        }

        /// <summary>
        /// Funzione eseguita dai thread per trovare i rettangoli modificati all'interno delle bitmap last e current
        /// </summary>
        /// <param name="lastBitmap"></param>
        /// <param name="currentBitmap"></param>
        /// <param name="rect">Area da analizzare</param>
        /// <returns></returns>
        public static Rectangle ComparePixels(Int32 []lastPixels, Int32[]currentPixels, Rectangle rect, int bmpHeight, int bmpWidth)
        {
            int minX = -1, maxX = -1;
            int minY = -1, maxY = -1;
            
            for (int i = rect.Left; i < rect.Right; i++)
            {
                for (int j = rect.Top; j  < rect.Bottom; j++)
                {
                    //int index = (bmpHeight - j - 1) * bmpWidth + i;
                    int index = j * bmpWidth + i;
                    if (lastPixels[index] != currentPixels[index])
                    {
                        if (minX == -1 || i < minX)
                            minX = i;
                        else if (maxX == -1 || i > maxX)
                            maxX = i;
                        if (minY == -1 || j < minY)
                            minY = j;
                        else if (maxY == -1 || j > maxY)
                            maxY = j;
                    }
                }
            }

            if (minX != -1)
            {
                //Aumento leggermente il rettangolo in modo da rendere più bello il risultato
                if (minX > 5)
                    minX -= 5;
                else
                    minX = 0;
                if(minY > 5)
                    minY -= 5;
                else
                    minY = 0;
                maxX += 5;
                maxY += 5;
            }

            Rectangle ris = new Rectangle(minX, minY, maxX - minX, maxY - minY);
            return ris;
        }
    }
}
